package com.bg.zk.lock;

import com.bg.constant.Constants;
import com.bg.utisl.PropertieConfigurationUtil;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @version V1.0
 * @Title: DistributedLock.java
 * @Package:com.getHub.zklock
 * @Description:zk分布式锁
 * @author: zhengshunzhi
 * @date 2018年2月26日 下午4:09:54
 */
// 引言
//
// ZooKeeper是一个分布式的，开放源码的分布式应用程序协调服务，是Google的Chubby一个开源的实现，是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件，提供的功能包括：配置维护、域名服务、分布式同步、组服务等。
//
// ZooKeeper的架构通过冗余服务实现高可用性。因此，如果第一次无应答，客户端就可以询问另一台ZooKeeper主机。ZooKeeper节点将它们的数据存储于一个分层的命名空间，非常类似于一个文件系统或一个前缀树结构。客户端可以在节点读写，从而以这种方式拥有一个共享的配置服务。更新是全序的。
//
// 基于ZooKeeper分布式锁的流程
//
// 在zookeeper指定节点（locks）下创建临时顺序节点node_n
// 获取locks下所有子节点children
// 对子节点按节点自增序号从小到大排序
// 判断本节点是不是第一个子节点，若是，则获取锁；若不是，则监听比该节点小的那个节点的删除事件
// 若监听事件生效，则回到第二步重新进行判断，直到获取到锁
// 具体实现
//
// 下面就具体使用java和zookeeper实现分布式锁，操作zookeeper使用的是apache提供的zookeeper的包。
//
// 通过实现Watch接口，实现process(WatchedEvent
// event)方法来实施监控，使CountDownLatch来完成监控，在等待锁的时候使用CountDownLatch来计数，等到后进行countDown，停止等待，继续运行。
// 以下整体流程基本与上述描述流程一致，只是在监听的时候使用的是CountDownLatch来监听前一个节点。

public class ZkDistributedLock implements Lock, Watcher {
    private ZooKeeper zk = null;

    // 竞争的资源
    private String lockName;

    // 等待的前一个锁
    private String WAIT_LOCK;

    // 当前锁
    private String CURRENT_LOCK;

    // 计数器
    private CountDownLatch countDownLatch;

    //存储异常的集合
    private List<Exception> exceptionList = new ArrayList<Exception>();

    //超时时间
    private int timeOut = PropertieConfigurationUtil.getInteger(Constants.ZK_SESSION_TIMEOUT);

    //锁的根节点
    private String  rootLock = PropertieConfigurationUtil.getPropertie(Constants.ZK_ROOT_LOCK);

    /**
     * 配置分布式锁
     *
     * @param config         连接的url
     * @param lockPrefixName 竞争资源
     */
    public ZkDistributedLock(String config, String lockPrefixName) {
        this.lockName = lockPrefixName;
        try {
            // 连接zookeeper
            zk = new ZooKeeper(config,timeOut , this);
            Stat stat = zk.exists(rootLock, false);
            if (stat == null) {
                // 如果根节点不存在，则创建根节点
                zk.create(rootLock, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    // 节点监视器
    public void process(WatchedEvent event) {
        if (this.countDownLatch != null) {
            this.countDownLatch.countDown();
        }
    }

    public void lock() {
        //判断是否有异常产生
        if (exceptionList.size() > 0) {
            throw new LockException(exceptionList.get(0));
        }

        try {
            //尝试去获取锁
            if (this.tryLock()) {
                System.out.println(Thread.currentThread().getName() + " " + lockName + "获得了锁");
                return;
            } else {
                // 等待锁
                waitForLock(WAIT_LOCK, timeOut);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    public boolean tryLock() {
        try {
            String splitStr = "_lock_";

            if (lockName.contains(splitStr)) {
                throw new LockException("锁名有误");
            }

            // 创建临时有序节点
            CURRENT_LOCK = zk.create(rootLock + "/" + lockName + splitStr, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            System.out.println(CURRENT_LOCK + " 已经创建");

            // 取所有子节点
            List<String> subNodes = zk.getChildren(rootLock, false);
            // 取出所有lockName的锁
            List<String> lockObjects = new ArrayList<String>();
            subNodes.forEach(node -> {
                String _node = node.split(splitStr)[0];
                if (_node.equals(lockName)) {
                    lockObjects.add(node);
                }
            });

            Collections.sort(lockObjects);
            System.out.println(Thread.currentThread().getName() + " 的锁是 " + CURRENT_LOCK);

            // 若当前节点为最小节点，则获取锁成功
            if (CURRENT_LOCK.equals(rootLock + "/" + lockObjects.get(0))) {
                return true;
            }

            // 若不是最小节点，则找到自己的前一个节点
            String prevNode = CURRENT_LOCK.substring(CURRENT_LOCK.lastIndexOf("/") + 1);
            WAIT_LOCK = lockObjects.get(Collections.binarySearch(lockObjects, prevNode) - 1);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
        return false;
    }

    public boolean tryLock(long timeout, TimeUnit unit) {
        try {
            if (this.tryLock()) {
                return true;
            }
            return waitForLock(WAIT_LOCK, timeout);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    // 等待锁
    private boolean waitForLock(String prev, long waitTime) throws KeeperException, InterruptedException {
        Stat stat = zk.exists(rootLock + "/" + prev, true);

        if (stat != null) {
            System.out.println(Thread.currentThread().getName() + "等待锁 " + rootLock + "/" + prev);
            this.countDownLatch = new CountDownLatch(1);
            // 计数等待，若等到前一个节点消失，则precess中进行countDown，停止等待，获取锁
            this.countDownLatch.await(waitTime, TimeUnit.MILLISECONDS);
            this.countDownLatch = null;
            System.out.println(Thread.currentThread().getName() + " 等到了锁");
        }
        return true;
    }

    public void unlock() {
        try {
            System.out.println("释放锁 " + CURRENT_LOCK);
            zk.delete(CURRENT_LOCK, -1);
            CURRENT_LOCK = null;
            zk.close();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    public Condition newCondition() {
        return null;
    }

    public void lockInterruptibly() throws InterruptedException {
        this.lock();
    }

    public class LockException extends RuntimeException {
        private static final long serialVersionUID = 1L;

        public LockException(String e) {
            super(e);
        }

        public LockException(Exception e) {
            super(e);
        }
    }
}